using System;

namespace NFreeMarker.Core
{
    /**
     * A class that allows one to associate custom data with a configuration, 
     * a template, or environment. It works pretty much like {@link ThreadLocal}, a
     * class that allows one to associate custom data with a thread.
     * @author Attila Szegedi
     * @version $Id: CustomAttribute.java,v 1.3 2003/11/10 14:05:22 ddekany Exp $
     */
    public class CustomAttribute
    {
        /**
         * Constant used in the constructor specifying that this attribute is
         * scoped by the environment.
         */
        public const int ScopeEnvironment = 0;

        /**
         * Constant used in the constructor specifying that this attribute is
         * scoped by the template.
         */
        public const int ScopeTemplate = 1;

        /**
         * Constant used in the constructor specifying that this attribute is
         * scoped by the configuration.
         */
        public const int ScopeConfiguration = 2;

        // We use an internal key instead of 'this' so that malicious subclasses 
        // overriding equals() and hashCode() can't gain access to other attribute
        // values. That's also the reason why get() and set() are marked final.
        private readonly object _key = new object();
        private readonly int _scope;

        /**
         * Creates a new custom attribute with the specified scope
         * @param scope one of <tt>SCOPE_</tt> constants. 
         */
        public CustomAttribute(int scope)
        {
            if (scope != ScopeEnvironment &&
                scope != ScopeTemplate &&
                scope != ScopeConfiguration)
            {
                throw new ArgumentException();
            }
            _scope = scope;
        }

        /**
         * This method is invoked when {@link #get()} is invoked without 
         * {@link #set(object)} being invoked before it to define the value in the 
         * current scope. Override it to create the attribute value on-demand.  
         * @return the initial value for the custom attribute. By default returns null.
         */
        protected internal object Create()
        {
            return null;
        }

        /**
         * @return the value of the attribute in the context of the current environment.
         * @throws IllegalStateException if there is no current environment (and
         * hence also no current template and configuration), therefore the
         * attribute's current scope object can't be resolved.
         */
        public object Get()
        {
            return GetScopeConfigurable().GetCustomAttribute(_key, this);
        }

        /**
         * @return the value of a template-scope attribute in the context of a 
         * given template.
         * @throws UnsupportedOperationException if this custom attribute is not a
         * template-scope attribute
         * @throws NullPointerException if t is null
         */
        public object Get(Template.Template t)
        {
            if (_scope != ScopeTemplate)
            {
                throw new InvalidOperationException("This is not a template-scope attribute");
            }
            return t.GetCustomAttribute(_key, this);
        }

        /**
         * Sets the value of the attribute in the context of the current environment.
         * @param value the new value of the attribute
         * @throws IllegalStateException if there is no current environment (and
         * hence also no current template and configuration), therefore the
         * attribute's current scope object can't be resolved.
         */
        public void Set(object value)
        {
            GetScopeConfigurable().SetCustomAttribute(_key, value);
        }

        /**
         * Sets the value of a template-scope attribute in the context of the given
         * template.
         * @param value the new value of the attribute
         * @param t the template 
         * @throws UnsupportedOperationException if this custom attribute is not a
         * template-scope attribute
         * @throws NullPointerException if t is null
         */
        public void Set(object value, Template.Template t)
        {
            if (_scope != ScopeTemplate)
            {
                throw new InvalidOperationException("This is not a template-scope attribute");
            }
            t.SetCustomAttribute(_key, value);
        }

        private Configurable GetScopeConfigurable()
        {
            Configurable c = Environment.GetCurrentEnvironment();
            if (c == null)
            {
                throw new InvalidOperationException("No current environment");
            }
            switch (_scope)
            {
                case ScopeEnvironment:
                    {
                        return c;
                    }
                case ScopeTemplate:
                    {
                        return c.GetParent();
                    }
                case ScopeConfiguration:
                    {
                        return c.GetParent().GetParent();
                    }
                default:
                    {
                        throw new Exception();
                    }
            }
        }
    }
}